﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics.CodeAnalysis;
using FluentNHibernate;
using FluentNHibernate.Automapping;
using FluentNHibernate.Cfg;
using FluentNHibernate.Conventions.Helpers;
using FluentNHibernate.Diagnostics;
using Framework.Core;
using Framework.Repository.Conventions;
using Framework.Repository.Testing;
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Engine;
using NHibernate.Linq;
using NHibernate.Metadata;
using NHibernate.Stat;

namespace Framework.Repository
{
    /// <summary>
    ///   Application must have only one instance of this class
    /// </summary>
    public sealed class SessionFactoryBuilder : ISessionFactoryBuilder
    {
        class TypeSourceAdapter : ITypeSource
        {
            private readonly IEnumerable<Type> types;

            public TypeSourceAdapter(IEnumerable<Type> types)
            {
                this.types = types;
            }

            public IEnumerable<Type> GetTypes()
            {
                return types;
            }

            [ExcludeFromCodeCoverage]
            public void LogSource(IDiagnosticLogger logger)
            {

            }

            [ExcludeFromCodeCoverage]
            public string GetIdentifier()
            {
                return "TypeSourceAdapter";
            }
        }

        [ExcludeFromCodeCoverage]
        class SessionFactory : ISessionFactory
        {
            private readonly ISessionFactory nhibernateSessionFactory;
            private readonly IPersistence persistence;

            public SessionFactory(ISessionFactory nhibernateSessionFactory, IPersistence persistence)
            {
                this.nhibernateSessionFactory = nhibernateSessionFactory;
                this.persistence = persistence;
            }

            public void Dispose()
            {
                nhibernateSessionFactory.Dispose();
                persistence.Dispose();
            }

            public ISession OpenSession(IDbConnection conn)
            {
                return nhibernateSessionFactory.OpenSession(conn);
            }

            public ISession OpenSession(IInterceptor sessionLocalInterceptor)
            {
                return nhibernateSessionFactory.OpenSession(sessionLocalInterceptor);
            }

            public ISession OpenSession(IDbConnection conn, IInterceptor sessionLocalInterceptor)
            {
                return nhibernateSessionFactory.OpenSession(conn, sessionLocalInterceptor);
            }

            public ISession OpenSession()
            {
                return nhibernateSessionFactory.OpenSession();
            }

            public IClassMetadata GetClassMetadata(Type persistentClass)
            {
                return nhibernateSessionFactory.GetClassMetadata(persistentClass);
            }

            public IClassMetadata GetClassMetadata(string entityName)
            {
                return nhibernateSessionFactory.GetClassMetadata(entityName);
            }

            public ICollectionMetadata GetCollectionMetadata(string roleName)
            {
                return nhibernateSessionFactory.GetCollectionMetadata(roleName);
            }

            public IDictionary<string, IClassMetadata> GetAllClassMetadata()
            {
                return nhibernateSessionFactory.GetAllClassMetadata();
            }

            public IDictionary<string, ICollectionMetadata> GetAllCollectionMetadata()
            {
                return nhibernateSessionFactory.GetAllCollectionMetadata();
            }

            public void Close()
            {
                nhibernateSessionFactory.Close();
            }

            public void Evict(Type persistentClass)
            {
                nhibernateSessionFactory.Evict(persistentClass);
            }

            public void Evict(Type persistentClass, object id)
            {
                nhibernateSessionFactory.Evict(persistentClass, id);
            }

            public void EvictEntity(string entityName)
            {
                nhibernateSessionFactory.EvictEntity(entityName);
            }

            public void EvictEntity(string entityName, object id)
            {
                nhibernateSessionFactory.EvictEntity(entityName, id);
            }

            public void EvictCollection(string roleName)
            {
                nhibernateSessionFactory.EvictCollection(roleName);
            }

            public void EvictCollection(string roleName, object id)
            {
                nhibernateSessionFactory.EvictCollection(roleName, id);
            }

            public void EvictQueries()
            {
                nhibernateSessionFactory.EvictQueries();
            }

            public void EvictQueries(string cacheRegion)
            {
                nhibernateSessionFactory.EvictQueries(cacheRegion);
            }

            public IStatelessSession OpenStatelessSession()
            {
                return nhibernateSessionFactory.OpenStatelessSession();
            }

            public IStatelessSession OpenStatelessSession(IDbConnection connection)
            {
                return nhibernateSessionFactory.OpenStatelessSession(connection);
            }

            public FilterDefinition GetFilterDefinition(string filterName)
            {
                return nhibernateSessionFactory.GetFilterDefinition(filterName);
            }

            public ISession GetCurrentSession()
            {
                return nhibernateSessionFactory.GetCurrentSession();
            }

            public IStatistics Statistics
            {
                get { return nhibernateSessionFactory.Statistics; }
            }

            public bool IsClosed
            {
                get { return nhibernateSessionFactory.IsClosed; }
            }

            public ICollection<string> DefinedFilterNames
            {
                get { return nhibernateSessionFactory.DefinedFilterNames; }
            }
        }

        class AutoMapConfiguration : DefaultAutomappingConfiguration
        {
            public override bool IsComponent(Type type)
            {
                return type.HasCustomAttribute<ComponentAttribute>();
            }
        }

        private readonly IAutoMappingTypeProvider autoMappingTypeProvider;
        private readonly IPersistence persistence;

        public SessionFactoryBuilder(IAutoMappingTypeProvider autoMappingTypeProvider, IPersistence persistence)
        {
            this.autoMappingTypeProvider = autoMappingTypeProvider;
            this.persistence = persistence;
        }

        public ISessionFactory Build()
        {
            Configuration config = null;

            var persistenceConfigurer = persistence.CreateConfigurer();

            var fluentConfiguration = Fluently.Configure()
                .Database(persistenceConfigurer)
                .Mappings(AddMappings)
                .ExposeConfiguration(c => SetConfiguration(c, out config));

            var sessionFactory = fluentConfiguration.BuildSessionFactory();

            persistence.BuildSchema(sessionFactory, config);

            return new SessionFactory(sessionFactory, persistence);
        }

        private static Configuration SetConfiguration(Configuration c, out Configuration config)
        {
            return config = c;
        }

        private AutoPersistenceModel GetAutoPersistenceModel()
        {
            var autoPersistenceModel = AutoMap.Assemblies(new AutoMapConfiguration());

            AddEntityAssemblies(autoPersistenceModel);
            AddConventions(autoPersistenceModel);
            AddAutoMappingOverride(autoPersistenceModel);

            return autoPersistenceModel;
        }

        private static void AddConventions(AutoPersistenceModel autoPersistenceModel)
        {
            autoPersistenceModel
                .Conventions.Add(DefaultLazy.Never())
                .Conventions.Add<CascadeAllConvention>()
                .Conventions.Add<CustomForeignKeyConvention>();
        }

        private void AddAutoMappingOverride(AutoPersistenceModel autoPersistenceModel)
        {
            autoMappingTypeProvider.GetOverrideTypes().ForEach(autoPersistenceModel.Override);
        }

        private void AddEntityAssemblies(AutoPersistenceModel autoPersistenceModel)
        {
            autoPersistenceModel.AddTypeSource(new TypeSourceAdapter(autoMappingTypeProvider.GetEntityTypes()));
            autoMappingTypeProvider.GetSubClassTypes().ForEach(sct => autoPersistenceModel.IncludeBase(sct));
        }

        private void AddMappings(MappingConfiguration m)
        {
            m.AutoMappings.Add(GetAutoPersistenceModel());
        }
    }
}